import Cesium from '../Ces/Cesium'

import Texture from './Texture'
import Sampler from './Sampler'
import Material from './Material'
import ImageryLayer from './ImageryLayer'
import PixelFormat from './PixelFormat'
import PixelDatatype from './PixelDatatype'
import ShadowMap from './ShadowMap'
import EntityView from './EntityView'
import TextureManager from './TextureManager'
import UniformType from './UniformType'
import CustomShader from './CustomShader'
import ShaderProgram from './ShaderProgram'
import VoxelPrimitive from './VoxelPrimitive'
// import LabelCollection from './LabelCollection'
import BatchTable from './BatchTable'
import FixCes from './FixCes'

function Inject() { }

Inject.init = function (customEntityView) {
  Cesium.ShaderProgram.prototype = ShaderProgram.prototype
  Cesium.TextureManager.prototype = TextureManager.prototype
  Cesium.VoxelPrimitive.prototype = VoxelPrimitive.prototype
  if (!Cesium['Texture2D']) {
    const texture2D = Cesium.Texture
    const MaterialRaw = Cesium.Material
    Object.defineProperty(Cesium, 'Texture2D', {
      get: () => texture2D,
    })
    Object.defineProperty(Cesium, 'Texture', {
      get: () => Texture,
    })
    Object.defineProperty(Cesium, 'BatchTable', {
      get: () => BatchTable,
    })
    Object.defineProperty(Cesium, 'Sampler', {
      get: () => Sampler,
    })
    Object.defineProperty(Cesium, 'MaterialRaw', {
      get: () => MaterialRaw,
    })
    Object.defineProperty(Cesium, 'Material', {
      get: () => Material,
    })
    Object.defineProperty(Cesium, 'PixelFormat', {
      get: () => PixelFormat,
    })
    Object.defineProperty(Cesium, 'PixelDatatype', {
      get: () => PixelDatatype,
    })
    Object.defineProperty(Cesium, 'UniformType', {
      get: () => UniformType,
    })
    Object.defineProperty(Cesium, 'CustomShader', {
      get: () => CustomShader,
    })

    //修正Cesium的一些问题
    FixCes()

    Inject.injectImageryLayer()
    if (customEntityView) {
      Inject.injectEntityView()
    }
  }
}

Inject.injectImageryLayer = function () {
  ImageryLayer.inject()
}

Inject.injectShadowMap = function () {
  if (Inject.injectShadowMapReady) {
    return
  }
  Inject.injectShadowMapReady = true

  Cesium.ShadowMap.createCastDerivedCommand = ShadowMap.createCastDerivedCommand
  Cesium.ShadowMap.createReceiveDerivedCommand = ShadowMap.createReceiveDerivedCommand

  if (Cesium.Version === '1.92') {
    Cesium.ShadowMap = ShadowMap
  } else {
    Object.defineProperty(Cesium, 'ShadowMap', {
      get: () => ShadowMap,
    })
  }

  Cesium.ShadowMapShader.createShadowCastVertexShader = function (vs, isPointLight, isTerrain) {
    const defines = vs.defines.slice(0)
    const sources = vs.sources.slice(0)

    defines.push('SHADOW_MAP')

    if (isTerrain) {
      defines.push('GENERATE_POSITION')
    }

    const positionVaryingName = Cesium.ShaderSource.findPositionVarying(vs)
    const hasPositionVarying = Cesium.defined(positionVaryingName)

    if (isPointLight && !hasPositionVarying) {
      const length = sources.length

      for (let j = 0; j < length; ++j) {
        sources[j] = Cesium.ShaderSource.replaceMain(sources[j], 'czm_shadow_cast_main')
      }

      let shadowVS = 'out vec3 v_positionEC; \n'

      shadowVS +=
        'uniform mat4 mainModelView; \n' +
        'void main() \n' +
        '{ \n' +
        '    czm_shadow_cast_main(); \n' +
        '    v_positionEC = (czm_inverseProjection * gl_Position).xyz; \n' +
        '}'

      sources.push(shadowVS)
    }

    return new Cesium.ShaderSource({
      defines,
      sources,
    })
  }

  Cesium.ShadowMapShader.createShadowCastFragmentShader = function (
    fs,
    isPointLight,
    usesDepthTexture,
    opaque,
  ) {
    const defines = fs.defines.slice(0)
    const sources = fs.sources.slice(0)

    let positionVaryingName = Cesium.ShaderSource.findPositionVarying(fs)
    const hasPositionVarying = Cesium.defined(positionVaryingName)

    if (!hasPositionVarying) {
      positionVaryingName = 'v_positionEC'
    }

    const length = sources.length

    for (let i = 0; i < length; ++i) {
      sources[i] = Cesium.ShaderSource.replaceMain(sources[i], 'czm_shadow_cast_main')
    }

    let fsSource = ''

    // if (isPointLight) {
    if (!hasPositionVarying) {
      fsSource += 'in vec3 v_positionEC; \n'
    }
    fsSource += 'uniform mat4 clipPlaneMatrix; \n'

    fsSource +=
      'void main() \n' +
      '{ \n' +
      '    czm_shadow_cast_main(); \n' +
      `    vec4 position = vec4(${positionVaryingName},1.0);\n` +
      '    float pixelWidth = czm_metersPerPixel(position);\n' +
      '    vec4 plane = vec4(vec3(0.0,0.0,1.0),0.0);\n' +
      '    vec4 clippingPlane = czm_transformPlane(plane, clipPlaneMatrix);\n' +
      '    vec3 clipNormal = clippingPlane.xyz;\n' +
      '    vec3 clipPosition = -clippingPlane.w * clipNormal;\n' +
      '    float amount = dot(clipNormal, (position.xyz - clipPosition)) / pixelWidth;\n' +
      '    if (amount <= 0.0)\n' +
      '    {\n' +
      '       discard;\n' +
      '    }\n'
    fsSource += '} \n'

    sources.push(fsSource)

    return new Cesium.ShaderSource({
      defines,
      sources,
    })
  }
}

Inject.injectEntityView = function () {
  if (Cesium.Version === '1.92') {
    Cesium.EntityView = EntityView
  } else {
    Object.defineProperty(Cesium, 'EntityView', {
      get: () => EntityView,
    })
  }

  function clearZoom(widget) {
    widget._zoomPromise = undefined
    widget._zoomTarget = undefined
    widget._zoomOptions = undefined
  }

  function cancelZoom(widget) {
    const zoomPromise = widget._zoomPromise

    if (Cesium.defined(zoomPromise)) {
      clearZoom(widget)
      widget._completeZoom(false)
    }
  }

  function updateZoomTarget(widget) {
    const target = widget._zoomTarget
    if (!Cesium.defined(target) || widget.scene.mode === SceneMode.MORPHING) {
      return
    }

    const scene = widget.scene
    const camera = scene.camera
    const zoomOptions = widget._zoomOptions ?? {}
    let options
    function zoomToBoundingSphere(boundingSphere) {
      // If offset was originally undefined then give it base value instead of empty object
      if (!Cesium.defined(zoomOptions.offset)) {
        zoomOptions.offset = new Cesium.HeadingPitchRange(0.0, -0.5, boundingSphere.radius)
      }

      options = {
        offset: zoomOptions.offset,
        duration: zoomOptions.duration,
        maximumHeight: zoomOptions.maximumHeight,
        complete: function () {
          widget._completeZoom(true)
        },
        cancel: function () {
          widget._completeZoom(false)
        },
      }

      if (widget._zoomIsFlight) {
        camera.flyToBoundingSphere(target.boundingSphere, options)
      } else {
        camera.viewBoundingSphere(boundingSphere, zoomOptions.offset)
        camera.lookAtTransform(Matrix4.IDENTITY)

        // Finish the promise
        widget._completeZoom(true)
      }

      clearZoom(widget)
    }

    if (target instanceof Cesium.TimeDynamicPointCloud) {
      if (Cesium.defined(target.boundingSphere)) {
        zoomToBoundingSphere(target.boundingSphere)
        return
      }

      // Otherwise, the first "frame" needs to have been rendered
      const removeEventListener = target.frameChanged.addEventListener(
        function (timeDynamicPointCloud) {
          zoomToBoundingSphere(timeDynamicPointCloud.boundingSphere)
          removeEventListener()
        },
      )
      return
    }

    if (target instanceof Cesium.Cesium3DTileset || target instanceof Cesium.VoxelPrimitive) {
      zoomToBoundingSphere(target.boundingSphere)
      return
    }

    // If zoomTarget was an ImageryLayer
    if (target instanceof Cesium.Cartographic) {
      options = {
        destination: scene.ellipsoid.cartographicToCartesian(target),
        duration: zoomOptions.duration,
        maximumHeight: zoomOptions.maximumHeight,
        complete: function () {
          widget._completeZoom(true)
        },
        cancel: function () {
          widget._completeZoom(false)
        },
      }

      if (widget._zoomIsFlight) {
        camera.flyTo(options)
      } else {
        camera.setView(options)
        widget._completeZoom(true)
      }
      clearZoom(widget)
      return
    }

    const entities = target

    const boundingSpheres = []
    for (let i = 0, len = entities.length; i < len; i++) {
      const state = widget._dataSourceDisplay.getBoundingSphere(
        entities[i],
        false,
        zoomTargetBoundingSphereScratch,
      )

      if (state === BoundingSphereState.PENDING) {
        return
      } else if (state !== BoundingSphereState.FAILED) {
        boundingSpheres.push(BoundingSphere.clone(zoomTargetBoundingSphereScratch))
      }
    }

    if (boundingSpheres.length === 0) {
      cancelZoom(widget)
      return
    }

    // Stop tracking the current entity.
    widget.trackedEntity = undefined

    const boundingSphere = BoundingSphere.fromBoundingSpheres(boundingSpheres)

    if (!widget._zoomIsFlight) {
      camera.viewBoundingSphere(boundingSphere, zoomOptions.offset)
      camera.lookAtTransform(Matrix4.IDENTITY)
      clearZoom(widget)
      widget._completeZoom(true)
    } else {
      clearZoom(widget)
      camera.flyToBoundingSphere(boundingSphere, {
        duration: zoomOptions.duration,
        maximumHeight: zoomOptions.maximumHeight,
        complete: function () {
          widget._completeZoom(true)
        },
        cancel: function () {
          widget._completeZoom(false)
        },
        offset: zoomOptions.offset,
      })
    }
  }

  const trackedEntityBoundingSphereScratch = new Cesium.BoundingSphere()
  function updateTrackedEntity(widget) {
    if (!widget._needTrackedEntityUpdate) {
      return
    }

    const trackedEntity = widget._trackedEntity
    const currentTime = widget.clock.currentTime

    //Verify we have a current position at this time. This is only triggered if a position
    //has become undefined after trackedEntity is set but before the boundingSphere has been
    //computed. In this case, we will track the entity once it comes back into existence.
    const currentPosition = Cesium.Property.getValueOrUndefined(trackedEntity.position, currentTime)

    if (!Cesium.defined(currentPosition)) {
      return
    }

    const scene = widget.scene

    const state = widget._dataSourceDisplay.getBoundingSphere(
      trackedEntity,
      false,
      trackedEntityBoundingSphereScratch,
    )
    if (state === Cesium.BoundingSphereState.PENDING) {
      return
    }

    const sceneMode = scene.mode
    if (sceneMode === Cesium.SceneMode.COLUMBUS_VIEW || sceneMode === Cesium.SceneMode.SCENE2D) {
      scene.screenSpaceCameraController.enableTranslate = false
    }

    if (sceneMode === Cesium.SceneMode.COLUMBUS_VIEW || sceneMode === Cesium.SceneMode.SCENE3D) {
      scene.screenSpaceCameraController.enableTilt = false
    }

    const bs =
      state !== Cesium.BoundingSphereState.FAILED ? trackedEntityBoundingSphereScratch : undefined
    widget._entityView = new EntityView(trackedEntity, scene, scene.ellipsoid)
    widget._entityView.update(currentTime, bs)
    widget._needTrackedEntityUpdate = false
  }

  Cesium.CesiumWidget.prototype._postRender = function () {
    updateZoomTarget(this)
    updateTrackedEntity(this)
  }
}

export default Inject
